/**
 * Copyright @2016-present, Sumscope, Inc.
 * All rights reserved.
 */

const uuid = require('node-uuid');
const Adapter = require('socket.io-adapter');
const msgpack = require('msgpack-js');
const redis = require('../../util/redis');

class SocketRedisAdaptor extends Adapter {
  
  constructor(options) {
    super(options);
    this.uid = uuid.v1();
    this.prefix = 'socket.io';
    this.sub = redis.createClient();
    this.setup();
  }
  
  setup() {
    this.sub.subscribe(this.getChannelName());
    this.sub.on('messageBuffer', this.onMessage.bind(this));
  }
  
  onMessage(channel, message) {
    let args = msgpack.decode(message);
    if (this.uid === args.shift()) {
      return;
    }
    let [packet, opts] = args;
    if (packet && packet.nsp === undefined) {
      packet.nsp = '/';
    }
    if (!packet || packet.nsp !== this.nsp.name) {
      return;
    }
    super.broadcast(packet, opts);
  }
  
  getChannelName() {
    let args = Array.from(arguments);
    args.unshift(this.prefix, this.nsp.name);
    return args.join('#') + '#';
  }
  
  broadcast(packet, opts) {
    super.broadcast(packet, opts);
    let msg = msgpack.encode([this.uid, packet, opts]);
    if (opts.rooms) {
      opts.rooms.forEach(room => redis.ops().publish(this.getChannelName(room), msg));
    } else {
      redis.publish(this.getChannelName(), msg);
    }
  }
  
  add(id, room, fn) {
    if (!this.rooms.hasOwnProperty(room)) {
      this.sub.subscribe(this.getChannelName(room), fn);
    }
    super.add(id, room);
    fn && fn();
  }
  
  del(id, room, fn) {
    super.del(id, room);
    let hasRoom = this.rooms.hasOwnProperty(room);
    if (hasRoom && !this.rooms[room]) {
      this.sub.unsubscribe(this.getChannelName(room), fn);
    } else if (fn) {
      process.nextTick(fn.bind(null, null));
    }
  }
  
  delAll(id, fn) {
    super.delAll(id, fn);
    let rooms = this.sids[id];
    if (!rooms && fn) {
      return process.nextTick(fn.bind(null, null));
    } else if (rooms) {
      let roomKeys = Object.keys(rooms).reduce((channels, room) => {
        if (!this.rooms[room]) {
          channels.push(this.getChannelName(room));
        }
        return channels;
      }, []);
      this.sub.unsubscribe(roomKeys, fn);
    }
  }
  
}

module.exports = SocketRedisAdaptor;
